/* 算法实现模板 */
import AlgorithmBase from "./AlgorithmBase";
import { loadDataset, txtToMatrix , mdRender} from "../../../common/Common";
import MatView2D, {createRandomPointStyleDict} from "../../../visual/MatView2D";
import Mat from "../../../common/Mat";
import { drawDecisionTree } from "../../../visual/Tree2D";

import 'katex/dist/katex.min.css';
import TDom from "../../../common/TDom";
import TCommon from "../../../common/TCommon";
import { showMathStringInTable } from "../../../visual/Table2D";

class BoostingTree extends AlgorithmBase{
  constructor(){
    super('BoostingTree');

    this.model = {C:0.2}

    this.descriptionUrl = `${this.homepage}/data/slm/ch8/8.3/README.md`;
    this.datasetUrl     = `${this.homepage}/data/slm/ch8/8.3/dataset.txt`;
    this.x = [];
    this.y = [];
  }

  init(){ 
    this.reset();

    this.initTrainDS();
  }

  reset(){
    this.dataset = undefined;
    this.trainDS = undefined;

    this.clear();

    this.initDescription();
    this.initTrainDS();
    this.updateModel();
  }

  // 模型UI
  initModelUI(){
    this.addLabel(this.domElementList.model, '损失误差:');
    this.etaInput = this.addFloatInput(
      this.domElementList.model, this.model.C, 
      (event)=>{
        const val = parseFloat(event.target.value);
        if(val<0){
          this.model.C = 0
          event.target.value = 0
        }
        else this.model.C = val;
        this.updateModel();
      });
    this.modelDiv = this.addDiv(this.domElementList.model);
  }

  // 渲染模型到页面
  updateModel(x, y){

    if(x === undefined) x = [0];
    if(y === undefined) y = [1];
 
    var input_log = []
    input_log.push([`序号:`])
    for(var i=0;i<x.length;i++){
      input_log[0].push(`${i}`)
    }

    input_log.push([`x:`])
    for(var i=0;i<x.length;i++){
      input_log[1].push(`${x[i]}`)
    }

    input_log.push([`y:`])
    for(var i=0;i<x.length;i++){
      input_log[2].push(`${y[i]}`)
    }

    if(this.inputDiv) this.inputDiv.innerHTML = '';
    showMathStringInTable(this.inputDiv, input_log);

    const modelString = String.raw
    `$$ \begin{aligned} C&:=${this.model.C} \end{aligned} $$`

    mdRender(`${modelString}`, this.outputDiv);
    
  }

  // 输入数据UI
  initInputUI(){

    // 文件选择器
    const fileSelector      = document.createElement('input');
    fileSelector.type       = 'file';
    fileSelector.hidden     = true;
    fileSelector.onchange   = this.handleFileChange.bind(this);   
    
    // this.addButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    TDom.createButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    this.addButton(this.domElementList.input, "使用本地数据", this.openFileSelector.bind(this));
    this.domElementList.input.appendChild(fileSelector);
    this.addButton(this.domElementList.input, "训练", this.train.bind(this));

    this.labelTrainTips = this.addLabel(this.domElementList.input, '');
    this.inputDiv       = this.addDiv(this.domElementList.input);

    this.fileSelector  = fileSelector;
  }

  // 输出UI
  initOutputUI(){
    this.outputDiv   = this.addDiv(this.domElementList.output);
    this.trainLogDiv = this.addDiv(this.domElementList.output);
  }

  // 获取描述md文件
  initDescription(){
    TDom.getDataFromUrl(this.descriptionUrl, (txt)=>{
      mdRender(txt, this.descriptionDiv);
    })

  }

  // 获取服务器数据集
  initTrainDS(){
    this.prepare(this.datasetUrl);
  }

  // 准备数据训练数据
  prepare(url){
    TDom.getDataFromUrl(url, 
      (txt)=>{
      this.dataset = txtToMatrix(txt);
      this.trainDS = TCommon.loadDataset(this.dataset);
      let X = new Mat.Mat(this.trainDS.Mat);
      let y = new Mat.Mat(this.trainDS.Labels);
      for(var i=0;i<y.length;i++){
        y[i]=y[i][0]
      }
      var x=[]
      for(var i=0;i<X.length;i++){
        x.push(X[i][0])
      }
      this.pointStyleDict = createRandomPointStyleDict(this.trainDS.Labels);
      this.updateModel(x,y);
    })
  }

  openFileSelector(){
    this.fileSelector.click();
  }
  // 获取文件路径加载
  handleFileChange(event){
    const files = event.target.files;

    for(let i = 0, il = files.length; i < il; i++){
      let reader = new FileReader();
      reader.onload = (e) => {      
        this.prepare(e.target.result);
        if(i === files.length - 1){
          event.target.value = '';
        }
      };
      reader.readAsDataURL(files[i])
    }
  }

  tree_c(x,y){
    var c1, c2, m, s
    for(var i=0;i<x.length-1;i++){
      var s_ = (x[i] + x[i+1])/2
      var r1_ = [], r2_ = [], c1_=0, c2_=0
      for(var j=0;j<x.length;j++){
        if(x[j]<s_){
          c1_ += y[j]
          r1_.push(j)
        }
        else{
          c2_ += y[j]
          r2_.push(j)
        }
      }
      c1_ = c1_/r1_.length
      c2_ = c2_/r2_.length
      var m_=0
      for(var k=0;k<i+1;k++) m_+=Math.pow(y[k]-c1_,2)
      for(var k=i+1;k<x.length;k++) m_+=Math.pow(y[k]-c2_,2)
      m_ = parseFloat(m_.toFixed(2))
      if(m == undefined){
        m = m_
        c1 = parseFloat(c1_.toFixed(2))
        c2 = parseFloat(c2_.toFixed(2))
        s = s_
      }
      else if(m_<m){
        m = m_
        c1 = parseFloat(c1_.toFixed(2))
        c2 = parseFloat(c2_.toFixed(2))
        s = s_
      }
    }
    var T = [[s],[c1,c2]]
  
    return T
  }
  
  get_tree(f_x, T){
    var f_x_ = []
    if(f_x.length!=0){
      var s = []
      var c = []
      var len = f_x[0].length
      var is_t = 0
      for(var i=0;i<len;i++){
        if(T[0][0]<f_x[0][i]){
          if(is_t==0){
            s.push(T[0][0])
            c.push(parseFloat((T[1][0]+f_x[1][i]).toFixed(2)))
            is_t=1
          }
          s.push(f_x[0][i])
          c.push(parseFloat((T[1][1]+f_x[1][i]).toFixed(2)))
        }
        else if(T[0][0]==f_x[0][i]){
          if(is_t == 0){
            s.push(T[0][0])
            c.push(parseFloat((T[1][0]+f_x[1][i]).toFixed(2)))
            is_t = 1
          }
        }
        else if(T[0][0]>f_x[0][i]){
          s.push(f_x[0][i])
          c.push(parseFloat((T[1][0]+f_x[1][i]).toFixed(2)))
          if(is_t==0&&i==len-1){
            s.push(T[0][0])
            c.push(parseFloat((T[1][0]+f_x[1][i]).toFixed(2)))
          }
        }
      }
      c.push(parseFloat((T[1][1]+f_x[1][len]).toFixed(2)))
      f_x_.push(s,c)
    }
    else f_x_ = T
  
    return f_x_
  }

  sort_x(x,y){
    var sort_index = x.length-1
    for(var i=0;i<x.length-1;i++){
      for(var j=0;j<sort_index;j++){
        if(x[j]>x[j+1]){
          var x_ = x[j]
          var y_ = y[j]
          x[j] = x[j+1]
          x[j+1] = x_
          y[j] = y[j+1]
          y[j+1] = y_
        }
      }
      sort_index--
    }
  
    return {'x':x,'y':y}
  }
  
  res_err(x,y,T){
    var r_er = []
    for(var i=0;i<x.length;i++){
      for(var j=0;j<T[0].length;j++){
        if(x[i]<T[0][j]){
          r_er.push(parseFloat((y[i] - T[1][j]).toFixed(2)))
          break
        }
        else if(x[i]==T[0][j]) r_er.push(parseFloat((y[i] - T[1][j+1]).toFixed(2)))
        else if(x[i]>T[0][j] && j==T[0].length-1) r_er.push(parseFloat((y[i] - T[1][j+1]).toFixed(2)))
      }
    }
  
    return r_er
  }
  
  cost_err(x,y,T){
    var c_er = 0
    for(var i=0;i<x.length;i++){
      for(var j=0;j<T[0].length;j++){
        if(x[i]<T[0][j]){
          c_er += parseFloat(Math.pow(y[i] - T[1][j],2).toFixed(2))
          break
        }
        else if(x[i]==T[0][j]) c_er += parseFloat(Math.pow(y[i] - T[1][j+1],2).toFixed(2))
        else if(x[i]>T[0][j] && j==T[0].length-1) c_er += parseFloat(Math.pow(y[i] - T[1][j+1],2).toFixed(2))
      }
    }
  
    return c_er
  }

  create_tree(x=[],y=[],f_x=[],tree={},is_l=0){
    if(Object.keys(tree).length == 0){
      tree['node'] = 'All'
      this.create_tree(x,y,f_x,tree)
    }
    else if(f_x[0].length>0){
      var min=Infinity,index=0
      for(var i=is_l;i<f_x[0].length;i++){
        var er=0
        for(var j=0;j<y.length;j++){
          if(x[j]<f_x[0][i]) er+=Math.pow(y[j]-f_x[1][i], 2)
          else er+=Math.pow(y[j]-f_x[1][i+1], 2)
        }
        if(er<min){
          min = er
          index = i
        }
      }
      var x_l=[],x_r=[],y_l=[],y_r=[],fx_l=[],fx_r=[]
      for(var i=0;i<x.length;i++){
        if(x[i]<f_x[0][index]){
          x_l.push(x[i])
          y_l.push(x[i])
        }
        else{
          x_r.push(x[i])
          y_r.push(x[i])
        }
      }
      fx_l.push(f_x[0].slice(0,index),f_x[1].slice(0,index+1))
      fx_r.push(f_x[0].slice(index+1),f_x[1].slice(index+1))
      var str_l = `<${f_x[0][index]}`, str_r = `>=${f_x[0][index]}`
      if(fx_l[0].length>0){
        tree[str_l] = {'node':'x' + str_l}
        this.create_tree(x_l,y_l,fx_l,tree[str_l])
      }
      else tree[str_l] = {'node':String(fx_l[1][0])}
      if(fx_r[0].length>0){
        tree[str_r] = {'node':'x' + str_r}
        this.create_tree(x_r,y_r,fx_r,tree[str_r],1)
      }
      else tree[str_r] = {'node':String(fx_r[1][0])} 
    }
  
    return tree
  }

  // 训练
  train(){
    const trainDS = this.trainDS;
    if(!trainDS) return;

    let X = new Mat.Mat(trainDS.Mat);
    let y = new Mat.Mat(trainDS.Labels);

    for(var i=0;i<y.length;i++){
      y[i]=y[i][0]
    }

    var x=[]
    for(var i=0;i<X.length;i++){
      x.push(X[i][0])
    }

    var res = this.sort_x(x,y)
    x = res.x
    y = res.y

    this.trainLog = []
    var count = 0
    this.trainLog.push(["迭代次数","回归树T(x)","提升树f_i(x)","平方损失误差"])

    var r = []
    for(var i=0;i<y.length;i++)r.push(y[i])
    var f_x = []
    var c_er = Infinity
    console.log(r);
    while(c_er>this.model.C){
      count++
      var T = this.tree_c(x,r)
      var f_x_ = this.get_tree(f_x.slice(), T)
      var r_er = this.res_err(x,y,f_x_.slice())
      c_er = this.cost_err(x,y,f_x_.slice())
      f_x = f_x_.slice()
      r = r_er.slice()
      var f_x_str = `f_{${count}}(x)=\\begin{cases}`
      for(var i=0;i<f_x[0].length;i++){
        if(i==0) f_x_str+=`${f_x[1][i]},~~~x<${f_x[0][i]}\\\\`
        else f_x_str+=`${f_x[1][i]},~~~${f_x[0][i-1]}\\leq x<${f_x[0][i]}\\\\`
      }
      f_x_str+=`${f_x[1][i]},~~~x \\geq ${f_x[0][f_x[0].length-1]} \\end{cases}`
      this.trainLog.push([`${count}`,`T_{${count}}(x) = \\begin{cases} ${T[1][0]},~~~x<${T[0][0]} \\\\${T[1][1]},~~~x \\geq ${T[0][0]} \\end{cases}`,f_x_str,`${c_er.toFixed(2)}`])
      if(count>30) break
    }

    var tree = this.create_tree(x,y,f_x)

    if(this.trainLogDiv) this.trainLogDiv.innerHTML = '';
    showMathStringInTable(this.trainLogDiv, this.trainLog);
    drawDecisionTree(this.trainLogDiv,tree);
    this.labelTrainTips.innerText = '训练完毕';
                
  }
}


export default BoostingTree;